package com.hna.eking.AirlineServer.security;

import java.awt.List;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;

import javax.print.attribute.standard.RequestingUserName;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import org.codehaus.groovy.util.Finalizable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;


import com.hna.eking.AirlineServer.Utils.MD5Util;

//@Configuration
//@EnableWebSecurity
 //可以指定多个HttpSecurity实例，用@Order指定优先级
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
	
	@Autowired
	CustomUserService customUserService;
	
	@Bean
	@Order(1)
	DeviceUserProcessingFilter deviceTokenProcessingFilter(){
		DeviceUserProcessingFilter deviceTokenProcessingFilter = new DeviceUserProcessingFilter();
		deviceTokenProcessingFilter.setAuthenticationManager(authenticationManager());
		deviceTokenProcessingFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
		deviceTokenProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
//		deviceTokenProcessingFilter.setRememberMeServices(tokenBasedRememberMeServices());
		deviceTokenProcessingFilter.setSessionAuthenticationStrategy(compositeSessionAuthenticationStrategy());
		return deviceTokenProcessingFilter;
	}
	
    @Bean
    SessionRegistry sessionRegistry(){
        SessionRegistry sessionRegistry = new SessionRegistryImpl();
        return sessionRegistry;
    }
    
    @Bean
    SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
        return new SimpleRedirectSessionInformationExpiredStrategy("/sessionExpired");
    }
    
    @Bean 
    RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy() {
    	return new RegisterSessionAuthenticationStrategy(sessionRegistry());
    }
    
    @Bean
    ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
    	return new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
    }
    
    @Bean
    ConcurrentSessionFilter concurrentSessionFilter() {
    	return new ConcurrentSessionFilter(sessionRegistry(), sessionInformationExpiredStrategy());
    }
    
    @Bean
    CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy() {
    	ArrayList<SessionAuthenticationStrategy> strategyList = new ArrayList<SessionAuthenticationStrategy>();
    	strategyList.add(registerSessionAuthenticationStrategy());
    	strategyList.add(concurrentSessionControlAuthenticationStrategy());
    	CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy = 
    		new CompositeSessionAuthenticationStrategy(strategyList);
    	return compositeSessionAuthenticationStrategy;
    }
	
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	
    	//使用and()方法相当于XML标签的关闭
        http
//        	.addFilterAt(deviceTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
        	.addFilterBefore(deviceTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
        	.addFilterAt(concurrentSessionFilter(),ConcurrentSessionFilter.class)
        	.authorizeRequests()
        	.antMatchers("/index", "/needlogin", "/file/*", "/hello").permitAll()
//        	.antMatchers("/hello").hasRole("USER")
        	.antMatchers("/api/*", "/logout").hasAnyRole("USER","ADMIN")

//        	.antMatchers("/api/*").hasIpAddress("10.71.202.91")
        	.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler("/accessDenied")) //登录后权限不够的处理方法
        	.and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint("/needlogin"))
        	.and()
        	//不加这句，将不会生成UsernamePasswordAuthenticationFilter实例，无法处理表单login POST请求
        	.formLogin().loginPage("/login").permitAll()
        	.and()
        	.logout().permitAll()
        	.and()
//        	.csrf().disable()
        	.sessionManagement().maximumSessions(1).expiredUrl("/needlogin") ;
            ;
//        http.addFilter(deviceTokenProcessingFilter());
        	
    }

//    @Autowired
//    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
////        auth
////            .inMemoryAuthentication()
////                .withUser("user").password("password").roles("USER");
//    	auth.userDetailsService(customUserService).passwordEncoder(passwordEncoder());
////    	auth.
//    }
    
    @Override
    protected AuthenticationManager authenticationManager() {
      ProviderManager authenticationManager = new ProviderManager(Arrays.asList(tokenAuthenticationProvider()));
      //不擦除认证密码，擦除会导致TokenBasedRememberMeServices因为找不到Credentials再调用UserDetailsService而抛出UsernameNotFoundException
      authenticationManager.setEraseCredentialsAfterAuthentication(false);
      return authenticationManager;
    }
    
    @Bean
    DeviceUserAuthenticationProvider tokenAuthenticationProvider(){
    	DeviceUserAuthenticationProvider tokenAuthenticationProvider = new DeviceUserAuthenticationProvider();
    	tokenAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    	tokenAuthenticationProvider.setCustomUserService(customUserService);
      return tokenAuthenticationProvider;
    }
    
    @Bean
    AuthenticationSuccessHandler authenticationSuccessHandler() {
//    	SimpleUrlAuthenticationSuccessHandler SuccessHandler = 
//    			new SimpleUrlAuthenticationSuccessHandler("/login/success");
    	
    	return new AuthenticationSuccessHandler(){
			@Override
			public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
					Authentication authentication) throws IOException, ServletException {
				response.setCharacterEncoding("UTF-8");  
			    response.setContentType("application/json; charset=utf-8"); 
			    PrintWriter out = null;  
			    try {  
			        out = response.getWriter();  
			        out.append("{\"result\":\"login success!\"}");
			    } catch (IOException e) {  
			        e.printStackTrace();  
			    } finally {  
			        if (out != null) {  
			            out.close();  
			        }  
			    }  
			}
    	};
    }

    @Bean
    AuthenticationFailureHandler authenticationFailureHandler() {
    	SimpleUrlAuthenticationFailureHandler failureHandler = 
    			new SimpleUrlAuthenticationFailureHandler("/login/failure");
    	failureHandler.setUseForward(true);
    	return failureHandler;
    }
    
    @Bean
    AccessDeniedHandler accessDeniedHandler(final String url) {
    	return new AccessDeniedHandler() {	
    		@Override
			public void handle(HttpServletRequest request, HttpServletResponse response,
    				AccessDeniedException accessDeniedException) throws IOException,
    				ServletException {
    			RequestDispatcher dispatcher = request.getRequestDispatcher(url);
    			dispatcher.forward(request, response);
    		}
    	};

    }
    
    @Bean
    AuthenticationEntryPoint authenticationEntryPoint(String url) {
		Assert.isTrue(
				StringUtils.hasText(url)
						&& UrlUtils.isValidRedirectUrl(url),
				"loginFormUrl must be specified and must be a valid redirect URL");
    	return new CustomAuthenticationEntryPoint(url);
    }
    
    
    @Bean
    BCryptPasswordEncoder passwordEncoder() {
    	return new BCryptPasswordEncoder(){
            @Override
            public String encode(CharSequence rawPassword) {
                return MD5Util.encode((String)rawPassword);
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.equals(MD5Util.encode((String)rawPassword));
            };
    	};
    }
    
}